﻿using System.Diagnostics.Contracts;
using System.Text;

namespace Devonline.Core;

/// <summary>
/// byte[] 类相关扩展方法
/// </summary>
public static class ByteExtensions
{
    /// <summary>
    /// 数字转换为16进制显示的字符
    /// </summary>
    /// <param name="i"></param>
    /// <returns></returns>
    public static char GetHexValue(this int i)
    {
        Contract.Assert(i >= 0 && i < 16, "i is out of range.");
        if (i < 10)
        {
            return (char)(i + '0');
        }

        return (char)(i - 10 + 'A');
    }
    /// <summary>
    /// Converts an array of bytes into a String.  
    /// </summary>
    /// <param name="value"></param>
    /// <param name="startIndex"></param>
    /// <param name="length"></param>
    /// <param name="spliter"></param>
    /// <returns></returns>
    /// <exception cref="ArgumentNullException"></exception>
    /// <exception cref="ArgumentOutOfRangeException"></exception>
    /// <exception cref="ArgumentException"></exception>
    public static string ToString(this byte[] value, int startIndex, int length, char spliter = AppSettings.CHAR_SPACE)
    {
        if (value == null)
        {
            throw new ArgumentNullException(nameof(value));
        }

        if (startIndex < 0 || startIndex >= value.Length && startIndex > 0)
        {  // Don't throw for a 0 length array.
            throw new ArgumentOutOfRangeException(nameof(startIndex), "start index out of range");
        }

        if (length < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(length), "generic positive out of range");
        }

        if (startIndex > value.Length - length)
        {
            throw new ArgumentException("arg array plus off too small");
        }

        Contract.EndContractBlock();

        if (length == 0)
        {
            return string.Empty;
        }

        var maxValue = int.MaxValue / 3;
        if (length > maxValue)
        {
            // (Int32.MaxValue / 3) == 715,827,882 Bytes == 699 MB
            throw new ArgumentOutOfRangeException(nameof(length), "argument out of range length too large:" + maxValue);
        }

        int chArrayLength = length * 3;
        char[] chArray = new char[chArrayLength];
        int index = startIndex;
        for (int i = 0; i < chArrayLength; i += 3)
        {
            byte b = value[index++];
            chArray[i] = GetHexValue(b / 16);
            chArray[i + 1] = GetHexValue(b % 16);
            chArray[i + 2] = spliter;
        }

        // We don't need the last spliter character
        return new string(chArray, 0, chArray.Length - 1);
    }
    /// <summary>
    /// Converts an array of bytes into a String
    /// </summary>
    /// <param name="value"></param>
    /// <param name="spliter"></param>
    /// <returns></returns>
    /// <exception cref="ArgumentNullException"></exception>
    public static string ToString(this byte[] value, char spliter = AppSettings.CHAR_SPACE)
    {
        if (value == null)
            throw new ArgumentNullException(nameof(value));
        Contract.Ensures(Contract.Result<string>() != null);
        Contract.EndContractBlock();
        return ToString(value, 0, value.Length, spliter);
    }
    /// <summary>
    /// Converts an array of bytes into a String
    /// </summary>
    /// <param name="value"></param>
    /// <param name="startIndex"></param>
    /// <param name="spliter"></param>
    /// <returns></returns>
    /// <exception cref="ArgumentNullException"></exception>
    public static string ToString(this byte[] value, int startIndex, char spliter = AppSettings.CHAR_SPACE)
    {
        if (value == null)
            throw new ArgumentNullException(nameof(value));
        Contract.Ensures(Contract.Result<string>() != null);
        Contract.EndContractBlock();
        return ToString(value, startIndex, value.Length - startIndex, spliter);
    }

    /// <summary>
    /// 字节流转字符串数组
    /// </summary>
    /// <param name="bytes"></param>
    /// <param name="spliter"></param>
    /// <returns></returns>
    public static string ToHexString(this IEnumerable<byte> bytes, char spliter = AppSettings.CHAR_SPACE)
    {
        var value = BitConverter.ToString(bytes.ToArray());
        if (spliter != AppSettings.CHAR_HLINE)
        {
            value = value.Replace(AppSettings.CHAR_HLINE, spliter);
        }

        return value;
    }
    /// <summary>
    /// 字符串数组转字节流
    /// </summary>
    /// <param name="hexString"></param>
    /// <param name="spliter"></param>
    /// <returns></returns>
    public static byte[]? ToBinary(this string hexString, char spliter = AppSettings.CHAR_SPACE)
    {
        if (spliter == char.MinValue)
        {
            var bytes = new byte[hexString.Length / AppSettings.UNIT_TWO];
            for (int i = 0; i < bytes.Length; i++)
            {
                bytes[i] = Convert.ToByte(hexString[(i * AppSettings.UNIT_TWO)..(i * AppSettings.UNIT_TWO + AppSettings.UNIT_TWO)], AppSettings.UNIT_SIXTEEN);
            }

            return bytes;
        }
        else
        {
            var binaryArray = hexString.Split(spliter);
            if (binaryArray.Length > 0)
            {
                var bytes = new byte[binaryArray.Length];
                for (int i = 0; i < binaryArray.Length; i++)
                {
                    if (binaryArray[i].IsNotNullOrEmpty())
                    {
                        bytes[i] = Convert.ToByte(binaryArray[i], AppSettings.UNIT_SIXTEEN);
                    }
                }

                return bytes;
            }
        }

        return null;
    }

    /// <summary>
    /// 从字节流按顺序读取信息组织成对象
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="bytes"></param>
    /// <param name="encoding"></param>
    /// <param name="trim">是否过滤去掉多余空格</param>
    /// <returns></returns>
    public static T? GetTo<T>(this byte[] bytes, Encoding? encoding = default, bool trim = true) where T : class, new()
    {
        var fields = typeof(T).GetFieldAttributes();
        if (fields != null && fields.Any())
        {
            fields = fields.OrderBy(x => x.Index).ToList();
            var t = new T();
            var index = 0;
            encoding ??= Encoding.UTF8;
            foreach (var field in fields)
            {
                if (field != null && bytes.Length > field.Size)
                {
                    var value = encoding.GetString(bytes, index, field.Size);
                    if (!string.IsNullOrWhiteSpace(field.Name) && !string.IsNullOrWhiteSpace(value))
                    {
                        if (trim)
                        {
                            value = value.Trim();
                        }

                        t.SetPropertyValue(field.Name, value, false, field.Format);
                    }

                    index += field.Size;
                }
            }

            return t;
        }

        return default;
    }
}